// StarSystemPanel.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using Microsoft.Practices.Composite.Presentation;
using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.ServiceLocation;

using Supremacy.Client;
using Supremacy.Game;
using Supremacy.Messages;
using Supremacy.Messaging;
using Supremacy.Resources;
using Supremacy.Types;
using Supremacy.Universe;
using Supremacy.Xna;

using Wintellect.PowerCollections;

using System.Linq;

namespace Supremacy.UI
{
    public sealed class StarSystemPanel : Control
    {
        #region Constants
        private const double PlanetBonusIconSize = 20;
        private const double SystemBonusIconSize = 24;
        #endregion

        #region Static Members

        private static IClientContext s_clientContext;
        private static IResourceManager s_resourceManager;

        #endregion

        #region Constants
        private static readonly CachedBitmap DilithiumBonusImage;
        private static readonly CachedBitmap EnergyBonusImage;
        private static readonly CachedBitmap FoodBonusImage;
        private static readonly CachedBitmap NebulaImage;
        private static readonly CachedBitmap RawMaterialsBonusImage;
        public static readonly DependencyProperty ShowStatsProperty;
        #endregion

        #region Constructors and Finalizers
        static StarSystemPanel()
        {
            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
            //This style is defined in themes\generic.xaml
            //DefaultStyleKeyProperty.OverrideMetadata(typeof(SystemPanel), new FrameworkPropertyMetadata(typeof(SystemPanel)));

            FocusVisualStyleProperty.OverrideMetadata(
                typeof(StarSystemPanel),
                new FrameworkPropertyMetadata(
                    null,
                    (d, baseValue) => null));

            ShowStatsProperty = DependencyProperty.Register(
                "ShowStats",
                typeof(bool),
                typeof(StarSystemPanel),
                new PropertyMetadata(
                    true,
                    ShowStatsChangedCallback));

            NebulaImage = new CachedBitmap(
                new BitmapImage(
                    new Uri(
                        "Resources/Images/Stars/Nebula.png",
                        UriKind.Relative)),
                BitmapCreateOptions.None,
                BitmapCacheOption.OnLoad);
            NebulaImage.Freeze();

            FoodBonusImage = new CachedBitmap(
                new BitmapImage(
                    new Uri(
                        "Resources/Images/Resources/food.png",
                        UriKind.Relative)),
                BitmapCreateOptions.None,
                BitmapCacheOption.OnLoad);
            FoodBonusImage.Freeze();

            EnergyBonusImage = new CachedBitmap(
                new BitmapImage(
                    new Uri(
                        "Resources/Images/Resources/energy.png",
                        UriKind.Relative)),
                BitmapCreateOptions.None,
                BitmapCacheOption.OnLoad);
            EnergyBonusImage.Freeze();

            DilithiumBonusImage = new CachedBitmap(
                new BitmapImage(
                    new Uri(
                        "Resources/Images/Resources/dilithium.png",
                        UriKind.Relative)),
                BitmapCreateOptions.None,
                BitmapCacheOption.OnLoad);
            DilithiumBonusImage.Freeze();

            RawMaterialsBonusImage = new CachedBitmap(
                new BitmapImage(
                    new Uri(
                        "Resources/Images/Resources/rawmaterials.png",
                        UriKind.Relative)),
                BitmapCreateOptions.None,
                BitmapCacheOption.OnLoad);
            RawMaterialsBonusImage.Freeze();
        }
        #endregion

        #region Public and Protected Methods
        public static void PreloadImages()
        {
            PlanetView3D.PreloadImages();
            AsteroidsView.PreloadImages();
        }
        #endregion

        #region Private Methods
        private static void ShowStatsChangedCallback(
            DependencyObject source,
            DependencyPropertyChangedEventArgs e)
        {
            var panel = source as StarSystemPanel;
            if (panel != null)
                panel.Refresh();
        }
        #endregion

        #region Fields
        private readonly Grid _grid;
        private readonly ObservableObject<object> _regionContext;
        private readonly DelegatingWeakPropertyChangedListener _regionContextChangeHandler;

        #endregion

        #region Constructors and Finalizers
        public StarSystemPanel()
        {
            _grid = new Grid();
            _grid.ColumnDefinitions.Add(new ColumnDefinition());
            _grid.ColumnDefinitions.Add(new ColumnDefinition());
            _grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Auto);
            _grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);

            SetResourceReference(
                FontFamilyProperty,
                ClientResources.DefaultFontFamilyKey);

            AddLogicalChild(_grid);
            AddVisualChild(_grid);

            _regionContextChangeHandler = new DelegatingWeakPropertyChangedListener(OnRegionContextChanged);

            _regionContext = RegionContext.GetObservableContext(this);

            this.DataContext = _regionContext;
            
            PropertyChangedEventManager.AddListener(
                _regionContext,
                _regionContextChangeHandler,
                "Value");

            Channel<TurnStartedMessage>.Public.ObserveOnDispatcher().Subscribe(_ => Refresh());
        }

        private void OnRegionContextChanged(object sender, PropertyChangedEventArgs e)
        {
            Refresh();
        }
        #endregion

        #region Properties and Indexers
        // ReSharper disable MemberCanBeMadeStatic.Local
        private IClientContext ClientContext
        {
            get
            {
                if (s_clientContext == null)
                    s_clientContext = ServiceLocator.Current.GetInstance<IClientContext>();
                return s_clientContext;
            }
        }

        private IResourceManager ResourceManager
        {
            get
            {
                if (s_resourceManager == null)
                    s_resourceManager = ServiceLocator.Current.GetInstance<IResourceManager>();
                return s_resourceManager;
            }
        }
        // ReSharper restore MemberCanBeMadeStatic.Local

        public Sector Sector
        {
            get { return _regionContext.Value as Sector; }
        }

        public bool ShowStats
        {
            get { return (bool)GetValue(ShowStatsProperty); }
            set { SetValue(ShowStatsProperty, value); }
        }

        public StarSystem System
        {
            get
            {
                var sector = this.Sector;
                if (sector != null)
                    return sector.System;
                return null;
            }
        }

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }
        #endregion

        #region Public and Protected Methods
        public void Refresh()
        {
            var system = this.System;

            _grid.Children.Clear();

            if (ShowStats)
                DisplayStats(system);

            if ((system != null) && IsScanned(system.Sector) && StarHelper.SupportsPlanets(system))
                DisplayVisuals(system);
        }

        protected override Visual GetVisualChild(int index)
        {
            return _grid;
        }
        #endregion

        #region Private Methods
        private void DisplayStats(StarSystem system)
        {
            var statsPanel = new StackPanel();
            var fontSize = new FontSizeConverter();
            var name = new TextBlock();
            var details = new TextBlock();

            name.FontFamily = FontFamily;
            name.FontSize = (double)fontSize.ConvertFrom("14pt");
            name.Foreground = Brushes.LightBlue;
            name.Margin = new Thickness(0, 0, 0, 14);

            details.FontFamily = FontFamily;
            details.FontSize = (double)fontSize.ConvertFrom("12pt");
            details.Foreground = Brushes.White;
            details.TextWrapping = TextWrapping.Wrap;
            details.TextTrimming = TextTrimming.WordEllipsis;

            statsPanel.SetValue(Grid.ColumnProperty, 0);
            statsPanel.Orientation = Orientation.Vertical;
            statsPanel.Margin = new Thickness(0, 0, 14, 0);
            statsPanel.CanHorizontallyScroll = false;
            if (!Double.IsNaN(ActualWidth))
                statsPanel.MaxWidth = ActualWidth;
            statsPanel.Children.Add(name);

            if (!IsScanned(Sector))
            {
                name.Text = this.ResourceManager.GetString("UNKNOWN_SECTOR");
            }
            else if ((system != null) && !IsExplored(Sector) && StarHelper.SupportsPlanets(system))
            {
                name.Text = this.ResourceManager.GetString("UNEXPLORED_SYSTEM");
            }
            else if (system != null)
            {
                int maxPopulation;
                Percentage growthRate;

                if (system.HasColony)
                {
                    growthRate = system.Colony.GrowthRate;
                    maxPopulation = system.Colony.MaxPopulation;
                }
                else
                {
                    var targetRace = this.ClientContext.LocalPlayerEmpire.Civilization.Race;
                    growthRate = system.GetGrowthRate(targetRace);
                    maxPopulation = system.GetMaxPopulation(targetRace);
                }

                switch (system.StarType)
                {
                    case StarType.BlackHole:
                        name.Text = "Black Hole";
                        details.Text = "The gravity well caused by the Black Hole"
                                       + " makes sensor readings of this sector unreliable."
                                       + "  Starships in this sector may be destroyed if they"
                                       + " drift too close to the event horizon.";
                        statsPanel.Children.Add(details);
                        break;

                    case StarType.Quasar:
                        name.Text = "Quasar";
                        break;

                    case StarType.NeutronStar:
                        name.Text = "Neutron Star";
                        break;

                    case StarType.RadioPulsar:
                        name.Text = "Radio Pulsar";
                        details.Text = "Radiative emissions from the Radio Pulsar"
                                       + " disrupt sensors in this sector and adjacent sectors."
                                       + "  As the Radio Pulsar rotates, its effects move"
                                       + " clockwise through the eight adjacent sectors.";
                        statsPanel.Children.Add(details);
                        break;

                    case StarType.XRayPulsar:
                        name.Text = "X-Ray Pulsar";
                        details.Text = "Radiative emissions from the X-Ray Pulsar"
                                       + " disrupt sensors in this sector.  Radiative pulses"
                                       + " are also obscure sensor readings in a ring of sectors"
                                       + " which expands and contracts around the pulsar.";
                        statsPanel.Children.Add(details);
                        break;

                    default:
                        var pop = new TextBlock();
                        var maxPop = new TextBlock();
                        var growth = new TextBlock();
                        var race = new TextBlock();
                        var orbitals = new TextBlock();

                        pop.FontFamily = FontFamily;
                        maxPop.FontFamily = FontFamily;
                        growth.FontFamily = FontFamily;
                        race.FontFamily = FontFamily;
                        orbitals.FontFamily = FontFamily;

                        pop.FontSize = maxPop.FontSize = growth.FontSize = race.FontSize =
                                                                           orbitals.FontSize =
                                                                           (double)fontSize.ConvertFrom("11pt");

                        pop.Foreground = maxPop.Foreground = growth.Foreground =
                                                             race.Foreground = orbitals.Foreground = Brushes.Beige;

                        if (system.StarType == StarType.Nebula)
                        {
                            name.Text = String.Format(
                                this.ResourceManager.GetString("NEBULA_NAME_FORMAT"),
                                system.Name);
                        }
                        else
                        {
                            name.Text = system.Name;
                        }

                        pop.Text = String.Format(
                            "{0}:  {1:#,##0}",
                            this.ResourceManager.GetString("SYSTEM_POPULATION"),
                            system.HasColony ? system.Colony.Population.CurrentValue : 0);

                        maxPop.Text = String.Format(
                            "{0}:  {1:#,##0}",
                            this.ResourceManager.GetString("SYSTEM_MAX_POPULATION"),
                            maxPopulation);
                        growth.Text = String.Format(
                            "{0}:  {1:0.#}%",
                            this.ResourceManager.GetString("SYSTEM_GROWTH_RATE"),
                            growthRate * 100);
                        race.Text = system.HasColony
                                        ? String.Format(
                                              "{0}:  {1}",
                                              this.ResourceManager.GetString("SYSTEM_INHABITANTS"),
                                              system.Colony.Inhabitants.PluralName)
                                        : this.ResourceManager.GetString("SYSTEM_UNINHABITED");
                        orbitals.Text = "Defenses: None";

                        statsPanel.Children.Add(race);
                        if (system.IsInhabited)
                            statsPanel.Children.Add(pop);
                        statsPanel.Children.Add(maxPop);
                        statsPanel.Children.Add(growth);
                        statsPanel.Children.Add(orbitals);
                        break;
                }
            }
            else
            {
                name.Text = this.ResourceManager.GetString("EMPTY_SPACE");
            }

            _grid.Children.Add(statsPanel);
        }

        private void DisplayVisuals(StarSystem system)
        {
            var starContainer = new Grid();
            var view = new Viewbox();
            var visuals = new StackPanel();

            starContainer.Margin = new Thickness(14, 0, 0, 0);
            starContainer.MaxHeight = 128;
            starContainer.MaxWidth = 128;
            starContainer.VerticalAlignment = VerticalAlignment.Center;
            starContainer.HorizontalAlignment = HorizontalAlignment.Right;

            view.HorizontalAlignment = HorizontalAlignment.Right;

            visuals.Orientation = Orientation.Horizontal;
            visuals.HorizontalAlignment = HorizontalAlignment.Right;
            visuals.VerticalAlignment = VerticalAlignment.Center;

            if (IsExplored(Sector))
            {
                foreach (var planet in Algorithms.Reverse(system.Planets))
                {
                    if (planet.PlanetType == PlanetType.Asteroids)
                    {
                        var asteroids = new AsteroidsView { Margin = new Thickness(0, 0, 14, 0) };
                        visuals.Children.Add(asteroids);
                    }
                    else
                    {
                        var planetContainer = new Grid();
                        var planet3d = new PlanetView3D(planet, system);
                        planetContainer.Margin = new Thickness(0, 0, 14, 0);
                        planetContainer.HorizontalAlignment = HorizontalAlignment.Right;
                        planetContainer.VerticalAlignment = VerticalAlignment.Center;
                        planetContainer.Children.Add(planet3d);
                        if (planet.HasFoodBonus)
                        {
                            var bonusIcon = new Image
                                            {
                                                Source = FoodBonusImage,
                                                Width = PlanetBonusIconSize,
                                                Height = PlanetBonusIconSize,
                                                Margin = new Thickness(
                                                    0,
                                                    0,
                                                    -PlanetBonusIconSize / 2,
                                                    PlanetBonusIconSize),
                                                HorizontalAlignment = HorizontalAlignment.Right,
                                                VerticalAlignment = VerticalAlignment.Bottom,
                                                ToolTip = GameContext.Current.Tables.EnumTables
                                                    [typeof(PlanetBonus).Name]
                                                    [PlanetBonus.Food.ToString()][0]
                                            };
                            planetContainer.Children.Add(bonusIcon);
                        }
                        else if (planet.HasEnergyBonus)
                        {
                            var bonusIcon = new Image
                                            {
                                                Source = EnergyBonusImage,
                                                Width = PlanetBonusIconSize,
                                                Height = PlanetBonusIconSize,
                                                Margin = new Thickness(
                                                    0,
                                                    0,
                                                    -PlanetBonusIconSize / 2,
                                                    PlanetBonusIconSize),
                                                HorizontalAlignment = HorizontalAlignment.Right,
                                                VerticalAlignment = VerticalAlignment.Bottom,
                                                ToolTip = GameContext.Current.Tables.EnumTables
                                                    [typeof(PlanetBonus).Name]
                                                    [PlanetBonus.Energy.ToString()][0]
                                            };
                            planetContainer.Children.Add(bonusIcon);
                        }
                        visuals.Children.Add(planetContainer);
                    }
                }
            }

            FrameworkElement star;
            string starToolTip = "";

            if (system.StarType == StarType.Nebula)
            {
                var starImage = new Image { Source = NebulaImage };
                star = starImage;
            }
            else
            {
                star = new SunView3D { StarType = system.StarType };
            }

            if (system.StarType == StarType.Nebula)
            {
                if (IsExplored(Sector))
                {
                    starToolTip = String.Format(
                        this.ResourceManager.GetString("NEBULA_NAME_FORMAT"),
                        system.Name);
                }
            }
            else
            {
                if (IsExplored(Sector))
                    starToolTip = system.Name + "\n";
                starToolTip += this.ResourceManager.GetString(
                    "STAR_TYPE_" + system.StarType.ToString().ToUpper());
            }

            starToolTip += "\n" + String.Format(
                                      this.ResourceManager.GetString("QUADRANT_FORMAT"),
                                      this.ResourceManager.GetString(
                                          "QUADRANT_" + system.Quadrant.ToString().ToUpperInvariant()),
                                      this.ResourceManager.GetString("QUADRANT"));

            starContainer.Children.Add(star);

            if (IsExplored(system.Sector))
            {
                if (system.HasRawMaterialsBonus)
                {
                    var bonusIcon = new Image
                                    {
                                        Source = RawMaterialsBonusImage,
                                        Width = SystemBonusIconSize,
                                        Height = SystemBonusIconSize,
                                        HorizontalAlignment = HorizontalAlignment.Left,
                                        VerticalAlignment = VerticalAlignment.Top,
                                        ToolTip = GameContext.Current.Tables.EnumTables
                                            [typeof(SystemBonus).Name]
                                            [SystemBonus.RawMaterials.ToString()][0]
                                    };
                    starContainer.Children.Add(bonusIcon);
                }
                if (system.HasDilithiumBonus)
                {
                    var bonusIcon = new Image
                                    {
                                        Source = DilithiumBonusImage,
                                        Width = SystemBonusIconSize,
                                        Height = SystemBonusIconSize,
                                        Margin = new Thickness(
                                            (system.HasRawMaterialsBonus ? SystemBonusIconSize : 0),
                                            0,
                                            0,
                                            0),
                                        HorizontalAlignment = HorizontalAlignment.Left,
                                        VerticalAlignment = VerticalAlignment.Top,
                                        ToolTip = GameContext.Current.Tables.EnumTables
                                            [typeof(SystemBonus).Name]
                                            [SystemBonus.Dilithium.ToString()][0]
                                    };
                    starContainer.Children.Add(bonusIcon);
                }
            }

            star.ToolTip = starToolTip;

            visuals.Children.Add(starContainer);

            view.SetValue(Grid.ColumnProperty, 1);
            view.Stretch = Stretch.Uniform;
            view.StretchDirection = StretchDirection.DownOnly;
            view.Child = visuals;
            _grid.Children.Add(view);
        }

        private bool IsExplored(Sector sector)
        {
            if (sector == null)
                return false;
            return this.ClientContext.LocalPlayerEmpire.MapData.IsExplored(sector.Location);
        }

        private bool IsScanned(Sector sector)
        {
            if (sector == null)
                return false;
            return this.ClientContext.LocalPlayerEmpire.MapData.IsScanned(sector.Location);
        }
        #endregion
    }
}